/*
Copyright (c) 2009-2010, Warwick Warp Limited
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the Warwick Warp Limited nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL WARWICK WARP LIMITED BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#ifndef WOPTIONS_UTF8_TRAITS_H_INCLUDED
#define WOPTIONS_UTF8_TRAITS_H_INCLUDED

#include <string>
#include <boost/cstdint.hpp>

namespace woptions {

/*!
	\brief Class for iterating over UTF-8 sequences in std::strings.
*/
class utf8_iterator
{
	typedef unsigned char uchar;
public:
	//! Iterator difference type
	typedef std::ptrdiff_t difference_type;
	//! Iterator value type
	typedef std::string value_type;
	//! Iterator pointer to value type
	typedef const value_type *pointer;
	//! Iterator reference to value type
	typedef const value_type& reference;    
	//! Iterator capabilities
	typedef std::forward_iterator_tag iterator_category;
	
	//! \brief Construct a UTF-8 iterator from a range of characters in a string
	utf8_iterator(
		std::string::const_iterator begin,
		std::string::const_iterator end) :
		m_char_start(begin),
		m_char_end(end),
		m_str_end(end),
		m_valid_char(find_char(m_char_start,m_char_end))
	{
	}

	//! \brief Move on to the next UTF-8 character
	utf8_iterator operator++()
	{
		m_char_start = m_char_end;
		m_char_end   = m_str_end;
		m_valid_char = find_char(m_char_start,m_char_end);
		return *this;
	}
	
	//! \brief Move on to the next UTF-8 character
	utf8_iterator operator++(int)
	{
		utf8_iterator it = *this;
		++(*this);
		return it;
	}
	
	//! \brief Determine if two UTF-8 iterators refer to the same position
	bool operator==(const utf8_iterator& other) const
	{
		return m_char_start == other.m_char_start;
	}
	
	//! \brief Determine if two UTF-8 iterators do not refer to the same position
	bool operator!=(const utf8_iterator& other) const
	{
		return m_char_start != other.m_char_start;
	}

	//! \brief Extract the sequence of bytes that make up this UTF-8 character
	value_type operator*() const
	{
		return std::string(m_char_start,m_char_end);
	}
	
	/*! 
		\brief Determine if this is a valid UTF-8 character, also returns false
		at the end of the sequence.
	*/
	bool is_valid_utf8() const
	{
		return m_valid_char;
	}
	
	/*!
		\brief Determine if this is the end of the sequence
	*/
	bool is_end() const
	{
		return m_char_start == m_char_end;
	}
	
private:
	static bool find_char(
		std::string::const_iterator& begin,
		std::string::const_iterator& end)
	{
		struct seq_s { uchar mask; uchar val; int len; };
		static const seq_s seq[] = {
			{0x80,0x00,1}, // 1 byte sequence 0xxxxxxx
			{0xE0,0xC0,2}, // 2 byte sequence 110xxxxx
			{0xF0,0xE0,3}, // 3 byte sequence 1110xxxx
			{0xF8,0xF0,4}, // 4 byte sequence 11110xxx
			{0xFC,0xF8,5}, // 5 byte sequence 111110xx
			{0xFE,0xFC,6}, // 6 byte sequence 1111110x
			};
		static const size_t n_seq = sizeof(seq)/sizeof(seq[0]);
		
		if(begin != end)
		{
			bool valid = false;
			std::string::const_iterator it = begin;
			
			for(size_t i=0;i<n_seq;++i)
			{
				if( (uchar(*it) & seq[i].mask) == seq[i].val )
				{
					valid = check_seq(it,end,seq[i].len);
					break;
				}
			}
				
			// Misplaced continuation bytes: 10xxxxxx
			// Invalid UTF-8:                1111111x
			if(it == begin)
			{
				++it;
			}
			
			end = it;
			return valid;
		}
		else
		{
			return false;
		}
	}
	
	static bool check_seq(
		std::string::const_iterator& begin,
		std::string::const_iterator end,
		size_t n)
	{
		static const uchar C_MASK   = 0xC0; // 11000000
		static const uchar C_VAL    = 0x80; // 10000000
		static const uchar masks[]  = {0x80,0xE0,0xF0,0xF8,0xFC,0xFE};
		static const size_t n_masks = sizeof(masks)/sizeof(masks[0]);
		
		if(n == 0 || n > n_masks)
			return false;
		
		// First byte
		boost::uint32_t bits = uchar(*begin) & ~masks[n-1];
		++begin;
		
		for(size_t i=1;i<n;++i,++begin)
		{
			if( begin == end ||                   // not enough bytes
				(uchar(*begin) & C_MASK) != C_VAL // not a continuation byte
				)
				return false;
				
			bits = (bits<<6) + (uchar(*begin)&~C_MASK);
		}
		
		// Check for overlong encodings
		if(bits < (1<< 7) && n > 1)
			return false;
		if(bits < (1<<11) && n > 2)
			return false;
		if(bits < (1<<16) && n > 3)
			return false;
		if(bits < (1<<21) && n > 4)
			return false;
		if(bits < (1<<26) && n > 5)
			return false;
		
		// Check for invalid code points
		if(bits >= 0xD800 && bits <= 0xDFFF)
			return false;
		if(bits == 0xFFFE || bits == 0xFFFF)
			return false;
		
		return true;
	}
	
	std::string::const_iterator m_char_start;
	std::string::const_iterator m_char_end;
	std::string::const_iterator m_str_end;
	bool                        m_valid_char;
};

/*!
	\brief Class for iterating over UTF-8 sequences in std::strings, skipping
	invalid characters.
*/
class utf8_skip_iterator : public utf8_iterator
{
public:
	//! \brief Construct an iterator over valid UTF characters in a string
	utf8_skip_iterator(
		std::string::const_iterator begin,
		std::string::const_iterator end) :
		utf8_iterator(begin,end) {}
	
	//! \brief Move on to the next valid UTF-8 character
	utf8_skip_iterator operator++()
	{
		do 
		{
			utf8_iterator::operator++();
		} while(!is_end() && !is_valid_utf8());
		return *this;
	}
	
	//! \brief Move on to the next valid UTF-8 character
	utf8_skip_iterator operator++(int)
	{
		utf8_skip_iterator it = *this;
		++(*this);
		return it;
	}
};

/*!
	\brief UTF-8 specific behaviour for parsing command line options.
	\ingroup traits
*/
struct utf8_traits
{
	//! \brief String type for the arguments
	typedef std::string arg_type;
	//! \brief Iterator type for iterating over flags in a flag list.
	typedef utf8_skip_iterator flag_iterator;
	
	//! Convert an argument to a narrow string for std::ostream and std::exception formatting
	static std::string to_char(const arg_type& s)
	{
		return std::string(s.begin(),s.end());
	}
	
	//! \return true if s is considered to be an option
	static bool is_option(const arg_type& s)
	{
		return s.length() >= 2 && s[0] == '-';
	}
	
	//! \return true if s could be a list of flags
	static bool is_flag_list(const arg_type& s)
	{
		return s.length() >= 2 && s[0] == '-' && s[1] != '-';
	}
	
	/*! 
		\brief split a parameterised option into its key and value components
		\return true if the key/value pair was successfully extracted
	*/
	static bool split(const arg_type& arg, arg_type& key, arg_type& value)
	{
		if(arg.length() >= 2)
		{
			size_t eqpos = arg.find('=');
			if(eqpos != arg_type::npos)
			{
				key   = arg.substr(0,eqpos);
				value = arg.substr(eqpos+1);
			}
			else
			{
				key   = arg.substr(0,2);
				value = arg.substr(2);
			}
			return true;
		}
		else
		{
			return false;
		}
	}
	
	/*!
		\brief Iterate over flags in a flag_list
	*/
	static flag_iterator begin_flags(const arg_type& arg)
	{
		if(arg.size() >= 2)
			return flag_iterator(arg.begin()+1,arg.end());
		else
			return flag_iterator(arg.end(),arg.end());
	}
	
	/*!
		\brief Iterate over flags in a flag_list
	*/
	static flag_iterator end_flags(const arg_type& arg)
	{
		return flag_iterator(arg.end(),arg.end());
	}
	
	/*!
		\brief Turn the a dereferenced flag iterator into the corresponding 
		option string for the flag.
	*/
	static arg_type make_flag_option(std::string f)
	{
		return "-" + f;
	}
};
	
} // namespace woptions

#endif
